「 Kata Containers 」源码走读 — virtcontainers
based on 3.0.0
virtcontainers 本质上不是独立组件,而是一个用于构建硬件虚拟化容器运行时的 Golang 库。
现有的少数部分基于 VM 的容器运行时都共享相同的硬件虚拟化语义,但是使用不同的代码库来实现,virtcontainers 的目标就是将这部分封装成一个通用的 Golang 库。
理想情况下,基于 VM 的容器运行时,会从将它们实现的运行时规范(例如 OCI spec 或 Kubernetes CRI)转换成 virtcontainers API。
virtcontainers API 大致受到 Kubernetes CRI 的启发。然而,尽管这两个项目之间的 API 相似,但 virtcontainers 的目标不是构建 CRI 实现,而是提供一个通用的、运行时规范不可知的、硬件虚拟化的容器库,其他项目可以利用它来自己实现 CRI。
VC
src/runtime/virtcontainers/interfaces.go
virtcontainers 库的入口模块,VC 初始化 VCSandbox 模块管理 sandbox,进而初始化 VCContainer 模块管理容器。
1 | type VCImpl struct { |
VC 中声明的 SetLogger 和 SetFactory 均为参数赋值,无复杂逻辑,不作详述。
CreateSandbox
创建 sandbox 与 pod_sandbox 容器
- 创建 sandbox
- 校验 sandboxConfig 的 annotation 中自定义运行时配置(称为 assets )的合法性并设置
annotation 例如 io.katacontainers.hypervisor.kernel 为用户上层通过 Pod annotation 定义,CRI 会透传给底层运行时 - 初始化 VCSandbox,准备所需环境
- 如果 sandbox 中状态信息(即 sandbox.state)已经存在,则表明不是新创建的 pod_sandbox 容器,无需后续动作,仅用于状态更新维护,直接返回 sandbox 即可
- 调用 fsShare 的 Prepare,准备 sandbox 所需的共享文件系统目录
- 调用 agent 的 createSandbox,准备 sandbox 所需环境
- 设置 sandbox 状态为 ready
- 校验 sandboxConfig 的 annotation 中自定义运行时配置(称为 assets )的合法性并设置
- 如果未启用 [runtime].disable_new_netns 并且不是 VM factory 场景(在 VM factory 场景下,网卡是在 VM 启动后热插进去的),则调用 network 的 AddEndpoints,添加 netns 中的所有网卡到 VM 中
netns 要么是 Kata Containers 在运行时创建,要么是 CNI 等外部组件预先创建。总之,此时 netns 已经存在了 - 调用 resCtrl 的 setupResourceController,将当前进程加入 cgroup 中管理
- 启动 VM(在 1-2 步骤中的 VCSandbox 初始化流程中,已经创建了 VM)
- 如果 [hypervisor].enable_debug 启用(用于输出 hypervisor 和 kernel 产生的消息),则调用 hypervisor 的 GetVMConsole,获取 VM console 地址(/run/vc/vm/<sandboxID>/console.sock)
- 调用 network 的 Run,进入到该 netns 中,执行以下逻辑:如果为 VM factory 场景,则获取 factory 中缓存的 VM,调用 agent 的 reuseAgent,更新 agent 实例,并创建软链接 /run/vc/vm/<sandboxID> 指向 /run/vc/vm/<vmID>;否则,调用 hypervisor 的 StartVM,启动 VM 进程
- 如果为 VM factory 场景,则调用 network 的 AddEndpoints,热添加 netns 中的所有网卡到 VM 中
- 如果启用 [hypervisor].enable_debug,实时读取 VM console 地址获取其实时内容,并以 debug 级别日志形式输出
- 调用 agent 的 startSandbox
- 调用 network 的 Endpoints,获取 VM 所有的网卡设备,调用 endpoint 的 NetworkPair,关闭位于 host 侧的 vhost_net 句柄(即 /dev/vhost-net)
截至 Kata 3.0,目前仅对 macvtap 类型的 endpoint 生效 - 调用 agent 的 getGuestDetails,获取如 seccompSupported 等 guest 信息详情,更新至 sandbox 中
- 创建 sandbox 中的每一个容器(其实,此时 sandbox 中仅有一个容器,就是 pod_sandbox 容器本身)
- 初始化 VCContainer,准备容器所需环境
- 根据 [hypervisor].disable_block_device_use、agent 是否具备使用块设备能力以及 hypervisor 是否允许块设备热插拔,判断是否当前支持块设备,并且容器的 rootfs 类型不是 fuse.nydus-overlayfs,也就是 rootfs 是基于块设备创建的
- 通过 /sys/dev/block/<major>-<minor>/dm 的存在性,判断是否为 devicemapper 块设备
- 如果是 devicemapper 块设备,则调用 devManager 的 NewDevice,初始化设备,并调用 devManager 的 AttachDevice,热插到 VM 中 <XDG_RUNTIME_DIR>/run/kata-containers/shared/containers/<sandboxID> 路径
- 针对容器中的每一个设备,调用 devManager 的 AttachDevice,attach 到 VM 中
- 调用 agent 的 createContainer,创建 pod_sandbox 容器
- 设置容器状态为 ready
- 调用 store 的 ToDisk,保存状态数据到文件中
- 更新维护在 sandbox 中的容器信息
- 调用 updateResources,热更新 VM 的资源规格(由于该流程中仅为创建 pod_sandbox,不涉及 pod_container,因此为配置中声明的 [hypervisor].default_vcpus 和 [hypervisor].default_memory)
- 调用 resCtrl 的 resourceControllerUpdate,更新 sandbox 的 cgroup
- 调用 store 的 ToDisk,保存状态数据到文件中
CleanupContainer
关停、删除容器并销毁 sandbox 环境
- 调用 store 的 FromDisk,读取 sandbox 状态信息
- 获取 sandbox 并更新其中的容器(更新的意义在于后续的删除操作以文件内容为准)
- 调用 VCSandbox 的 StopContainer 和 DeleteContainer,关停并删除该容器
- 调用 VCSandbox 的 GetAllContainers,获取 sandbox 中的所有容器,如果仍大于 0(说明当前 sandbox 仍有容器存在,需要保留 sandbox 环境),否则调用 VCSandbox 的 Stop,关停 sandbox,并调用 VCSandbox 的 Delete,删除 sandbox
VCSandbox
src/runtime/virtcontainers/interfaces.go
virtcontainers 库中用于管理 sandbox 的模块,同时调用 VCContainer 模块间接管理容器。
1 | // Sandbox is composed of a set of containers and a runtime environment. |
工厂函数
- 校验 [runtime].experimental 是否为可支持的特性
- 初始化 hypervisor、agent、store、fsSharer、devManager 等 virtcontainers 子模块
- 初始化 resourceController([runtime].sandbox_cgroup_only 为 true 表示 Pod 所有的线程全部由 sandboxController 管理;反之,仅 vCPU 线程由 sandboxController 管理,而其余的由 overheadController 管理)
- 获取到 spec.Linux.CgroupsPath(缺省为 /vc),如果 cgroup 不是由 systemd 纳管(通过 cgroupPath 格式判断),则最后一级路径新增 kata_ 前缀
- 获取 spec.Linux.Resources.Devices 中 /dev/null 和 /dev/urandom 设备信息(如果未声明,则构建)
- 调用 devManager 的 GetAllDevices,获取所有的设备;进一步调用 device 的 GetHostPath,获取设备位于 host 上的路径,将其构建成 cgroup 管理形式
- 调用 resCtrl 的 NewSandboxResourceController,初始化 sandboxController(cgroupPath 为步骤 1 处理后的结果)
- 如果 [runtime].sandbox_cgroup_only 为 false,则调用 resCtrl 的 NewResourceController,初始化 overheadController (cgroupPath 为 /kata_overhead/<sandboxID>)
- 从文件恢复 sandbox 状态信息
- 调用 store 的 FromDisk,尝试从文件中恢复 sandbox 状态信息
当 sandbox 新创建的时候,并没有状态文件,因此必然失败,但是会忽略错误信息 - 调用 hypervisor 的 Load,加载 hypervisor 信息
- 调用 devManager 的 LoadDevices,加载设备信息
- 调用 agent 的 load,加载 agent 信息
- 调用 endpoint 的 load,加载网络 endpoint 信息
- 调用 store 的 FromDisk,尝试从文件中恢复 sandbox 状态信息
- 校验 sandboxConfig 中的 hypervisor 配置的合法性,其中包括 [hypervisor].kernel 是否不为空,[hypervisor].image 和 [hypervisor].initrd 有且仅有一个。并设置 [hypervisor].default_vcpus 缺省时为 1,[hypervisor].default_memory 缺省时为 2048
- 调用 hypervisor 的 CreateVM,创建 VM
- 调用 agent 的 init,准备 agent 环境
VCSandbox 中声明的 Annotations、GetNetNs、GetAllContainers、GetAnnotations、GetContainer、ID 和 SetAnnotations 均为参数获取与赋值,无复杂逻辑,不作详述。
updateResources
热更新 VM 的资源规格
如果 [runtime].static_sandbox_resource_mgmt 启用时(Kata 将在启动虚拟机之前确定合适的 sandbox 内存和 CPU 大小,而非动态更新。 作为 hypervisor 不支持 CPU、内存热插拔时的解决方案),则不触发更新操作,直接返回
计算 sandbox 中所有状态非 stopped 容器的 CPU 、内存和 SWAP 总量(前提是 [hypervisor].enable_guest_swap 开启,并且 memory.swappiness 大于 0,才会考虑 SWAP 资源),加上基础 VM 大小,得出最后预期的 VM 大小,公式为
CPU = (C1.cpu-quota * 1000 / C1.cpu-period + 999) / 1000 + C2… + [hypervisor].default_vcpus
MEM = C1.memory-limit + C1.hugepages-limit + C2… + [hypervisor].default_memory
SWAP =
swap_in_bytes
memory_limit_in_bytes
swap size set set io.katacontainers.container.resource.swap_in_bytes
-memory_limit_in_bytes
not set set memory_limit_in_bytes
not set not set io.katacontainers.config.hypervisor.default_memory
set not set cgroup doesn’t support this usage 截至 Kata 3.0,在 K8s 场景下,SWAP 功能仍存在异常:参考 https://github.com/kata-containers/kata-containers/issues/5627
如果预期 SWAP 比当前 sandbox 的 SWAP 多,则需要新增一个大小为两者差值的 SWAP 文件
- 创建 /run/kata-containers/shared/sandboxes/swap<ID> 文件(sandbox 中的 SWAP 序号从 0 递增)
- 调整文件的大小为差值和 10 倍内存分页中最大值(小于 10 倍内页分页的 SWAP 会被 mkswap 拒绝:mkswap: error: swap area needs to be at least 40 KiB,内存分页:4096),并额外追加一个内存分页大小(SWAP 文件需要一个内存分页大小储存元数据)
- 调用系统命令 mkswap,转换为 SWAP 文件,并构建 raw 格式的块设备类型
- 调用 hypervisor 的 HotplugAddDevice,热添加 SWAP 文件到 VM 中
- 调用 agent 的 addSwap,配置 SWAP 文件
调用 hypervisor 的 ResizeVCPUs,调整 VM CPU 数量
如果为新增调整,则调用 agent 的 onlineCPUMem,通知 agent 上线热添加部分的 CPU
循环调用 hypervisor 的 GetTotalMemoryMB,获取当前 VM 的内存数量,比对预期 VM 的内存数量,在不超出最大热添加内存数量限制的前提下(部分场景下,例如 ACPI 热插拔,内存的单次热添加有最大数量限制;而在 virtio-mem 下,即 [hypervisor].enable_virtio_mem 启用, 且 /proc/sys/vm/overcommit_memory 文件内容为 1,则没有最大热添加数量限制),调用 hypervisor 的 ResizeMemory,分批热添加 VM 内存
调用 agent 的 memHotplugByProbe,通知 agent 内存热插事件(如果 guest 内核支持内存热添加探测),并调用 agent 的 onlineCPUMem,通知 agent 上线热添加部分的内存
Stats
获取 sandbox 的统计信息
- 调用 sandboxController 的 Stat,获取 sandbox 全部的 cgroup 统计信息(截至当前,并未聚合 kata_overhead 的统计信息,即 overheadController 部分)
- 调用 hypervisor 的 GetThreadIDs,获取 hypervisor 使用的 CPU 数量
- 聚合以上信息并返回
Start
启动 sandbox 与 pod_sandbox 容器
- 校验 sandbox 状态是否为 ready、paused 或 stopped
- 设置 sandbox 状态为 running
- 针对 sandbox 中的每一个容器,调用 VCContainer 的 start,启动容器(其实,此时 sandbox 中仅有一个容器,就是 pod_sandbox 容器本身)
- 调用 store 的 ToDisk,保存状态数据到文件中
Stop
关停 sandbox 与容器,并清理相关资源
- 如果 sandbox 状态已经为 stopped,则不做任何操作
- 校验 sandbox 状态是否为 ready、running 或者 paused
- 针对 sandbox 中的每一个容器,调用 VCContainer 的 stop,关停容器
- 调用 agent 的 stopSandbox,关停 sandbox
- 调用 hypervisor 的 StopVM,关停 VM
- 如果 [hypervisor]. enable_debug 启用,则关闭 VM console
- 设置 sandbox 状态为 stopped
- 调用 network 的 RemoveEndpoints,移除 VM 中的所有网卡
- 调用 store 的 ToDisk,保存状态数据到文件中
- 调用 agent 的 disconnect,关闭与 agent 的连接
- 移除 host 上的 /run/kata-containers/shared/sandboxes/swap<ID> 文件(sandbox 中的 SWAP 序号从 0 递增)
Delete
销毁 sandbox 与容器,并清理相关资源
- 校验 sandbox 的状态是否为 ready、paused 和 stopped
- 针对 sandbox 中的每一个容器,调用 VCContainer 的 delete,删除容器
- 如果在 root 权限下,则调用 resCtrl 的 resourceControllerDelete,删除相关的 resourceController 以及 cgroup 资源
- 关停 sandbox 的 monitor
- 调用 hypervisor 的 Cleanup,清理 hypervisor 资源
- 调用 fsShare 的 Cleanup,清理 sandbox 的共享文件系统
- 调用 store 的 Destroy,删除状态数据目录
Status
获取 sandbox 与容器的详细信息
- 针对 sandbox 中的每一个容器,获取其状态信息(例如:ID、rootfs、状态、启动时间、annotation、PID 等)
- 结合 sandbox 的状态信息(例如 ID、状态、hypervisor 类别、hypervisor 配置、annotation 等)返回
CreateContainer
创建 pod_container 容器并热更新 sandbox 规格
- 初始化 VCContainer,准备容器环境,挂载设备等
- 根据 [hypervisor].disable_block_device_use、agent 是否具备使用块设备能力以及 hypervisor 是否允许块设备热插拔,判断是否当前支持块设备,并且容器的 rootfs 类型不是 fuse.nydus-overlayfs,也就是 rootfs 是基于块设备创建的
- 通过 /sys/dev/block/<major>-<minor>/dm 的存在性,判断是否为 devicemapper 块设备
- 如果是 devicemapper 块设备,则调用 devManager NewDevice,初始化设备,并调用 devManager 的 AttachDevice,热插到 VM 中 <XDG_RUNTIME_DIR>/run/kata-containers/shared/containers/<sandboxID> 路径
- 针对容器中的每一个设备,调用 devManager 的 AttachDevice,热插到 VM 中
- 调用 agent 的 createContainer,创建 pod_container 容器
- 设置容器状态为 ready
- 调用 store 的 ToDisk,保存状态数据到文件中
- 更新维护在 sandbox 中的容器信息
- 调用 updateResources,热更新 VM 的资源规格
- 调用 resCtrl 的 resourceControllerUpdate,更新 sandbox 的 cgroup
- 调用 store 的 ToDisk,保存状态数据到文件中
DeleteContainer
删除 sandbox 中的指定容器
- 获取 sandbox 中的指定容器,并调用 VCContainer 的 delete,删除维护的容器信息
- 调用 resCtrl 的 resourceControllerUpdate,更新 sandbox 的 cgroup
- 调用 store 的 ToDisk,保存状态数据到文件中
StartContainer
启动 sandbox 中的 pod_container 容器
- 获取 sandbox 中的指定容器,并调用 VCContainer 的 start,启动容器
- 调用 store 的 ToDisk,保存状态数据到文件中
- 调用 updateResources,热更新 VM 的资源信息
StopContainer
关停 sandbox 中的指定容器
- 获取 sandbox 中的指定容器,并调用 VCContainer 的 stop,关停容器并清理相关资源
- 调用 store 的 ToDisk,保存状态数据到文件中
KillContainer
杀死 sandbox 中的指定容器进程
- 获取 sandbox 中的指定容器,并调用 VCContainer 的 signalProcess,发送 kill 信号(理论上是这样,然而并未有真实调用)
StatusContainer
获取 sandbox 中指定容器的状态
- 获取 sandbox 中的指定容器,获取其状态信息(例如:ID、rootfs、状态、启动时间、annotation、PID 等)
StatsContainer
获取 sandbox 中指定容器的统计信息
- 获取 sandbox 中的指定容器,校验其状态是否为 running
- 调用 agent 的 statsContainer,获取容器状态信息
PauseContainer
暂停 sandbox 中的指定容器
- 获取 sandbox 中的指定容器,校验其状态是否为 running
- 调用 agent 的 pauseContainer,暂停容器
- 设置容器状态为 paused
- 调用 store 的 ToDisk,保存状态数据到文件中
ResumeContainer
恢复 sandbox 中的指定容器
- 获取 sandbox 中的指定容器,校验其状态是否为 running
- 调用 agent 的 resumeContainer,恢复容器
- 设置容器状态为 running
- 调用 store 的 ToDisk,保存状态数据到文件中
EnterContainer
在 sandbox 中的指定容器中执行命令
- 获取 sandbox 中的指定容器,校验其状态是否为 running
- 调用 agent 的 exec,进入容器执行指定命令
UpdateContainer
更新 sandbox 中的指定容器资源规格
- 获取 sandbox 中的指定容器,校验其状态是否为 running
- 调用 updateResources,热更新 VM 的资源规格
- 调用 agent 的 updateContainer,更新容器
- 调用 resCtrl 的 resourceControllerUpdate,更新 sandbox 的 cgroup
- 调用 store 的 ToDisk,保存状态数据到文件中
WaitProcess
等待 sandbox 中的指定容器进程返回退出码
- 校验 sandbox 的状态是否为 running
- 获取 sandbox 中的指定容器,校验其状态是否为 ready 或 running
- 调用 agent 的 waitProcess,等待进程返回退出码
SignalProcess
向 sandbox 中的指定容器进程发送指定信号
- 校验 sandbox 的状态是否为 running
- 获取 sandbox 中的指定容器,调用 VCContainer 的 signalProcess,向容器进程发送指定信号
WinsizeProcess
设置 sandbox 中的指定容器 tty 大小
- 校验 sandbox 的状态是否为 running
- 获取 sandbox 中的指定容器,校验其状态是否为 ready 或 running
- 调用 agent 的 winsizeProcess,设置进程的 tty 大小
IOStream
获取 sandbox 中的指定容器 IO 流
- 校验 sandbox 的状态是否为 running
- 获取 sandbox 中的指定容器,校验其状态是否为 ready 或 running
- 初始化 IO 流,返回其 stdin、stdout 和 stderr
AddDevice
向 sandbox 中添加设备
- 调用 devManager 的 NewDevice,初始化对应类型的设备
- 调用 devManager 的 AttachDevice,添加该设备
AddInterface
向 sandbox 中添加网卡
- 将 rpc 请求体转换成网卡信息结构
- 调用 network 的 AddEndpoints,热添加该网卡
- 获取网卡 PCI 地址,调用 agent 的 updateInterface,更新网卡信息
- 调用 store 的 ToDisk,保存状态数据到文件中
RemoveInterface
移除 sandbox 中的指定网卡
- 调用 network 的 Endpoints,根据 MAC 地址匹配所有网卡中待移除的网卡
- 调用 network 的 RemoveEndpoints,移除该网卡
- 调用 store 的 ToDisk,保存状态数据到文件中
ListInterfaces
获取 sandbox 的所有网卡配置
- 调用 agent 的 listInterfaces,获取所有网卡配置
UpdateRoutes
更新 sandbox 的路由表
- 调用 agent 的 updateRoutes,更新路由表
ListRoutes
获取 sandbox 的所有路由配置
- 调用 agent 的 listRoutes,获取所有路由配置
GetOOMEvent
获取 sandbox 的 OOM 事件信息
- 调用 agent 的 getOOMEvent,获取 OOM 事件信息
GetHypervisorPid
获取 sandbox 的 hypervisor PID
- 调用 hypervisor 的 GetPids,获取所有 PID 列表
- 返回 PID 列表首位(因为首位是 hypervisor PID,次位为 virtiofsd PID)
UpdateRuntimeMetrics
更新 sandbox 的 hypervisor 相关指标
- 调用 hypervisor 的 GetPids,获取 hypervisor 的 PID
- 获取 /proc/<hypervisorPID>/fd 目录下的文件数量(进程打开的所有文件描述符,这些文件描述符是指向实际文件的一个符号链接,例如 0 表示 stdin、1 表示 stdout、2 表示 stderr 等等),上报 kata_hypervisor_fds 指标
- 解析 /proc/<hypervisorPID>/net/dev 文件内容(网络设备状态信息,例如 eth0、lo、tap0_kata 和 tunl0 接受和发送的数据包、错误和冲突的数量以及其他基本统计,参考 ifconfig 命令结果),上报 kata_hypervisor_netdev 指标
- 解析 /proc/<hypervisorPID>/stat 文件内容(进程的状态信息,参考 ps 命令结果),上报 kata_hypervisor_proc_stat 指标
- 解析 /proc/<hypervisorPID>/status 文件内容(进程的状态信息,相较于 /proc/<hypervisorPID>/stat 更易读),上报 kata_hypervisor_proc_status 指标
- 解析 /proc/<hypervisorPID>/io 文件内容(进程的 IO 统计信息),上报 kata_hypervisor_io 指标
- 调用 hypervisor 的 GetVirtioFsPid,获取 virtiofsd 的 PID
- 获取 /proc/<virtiofsdPID>/fd 目录下的文件数量,上报 kata_virtiofsd_fds 指标
- 解析 /proc/<virtiofsdPID>/stat 文件内容,上报 kata_virtiofsd_proc_stat 指标
- 解析 /proc/<hypervisorPID>/status 文件内容,上报 kata_virtiofsd_proc_status 指标
- 解析 /proc/<hypervisorPID>/io 文件内容,上报 kata_virtiofsd_io 指标
GetAgentMetrics
获取 sandbox 的 agent 相关指标
- 调用 agent 的 getAgentMetrics,获取 agent 的指标信息
GetAgentURL
获取 sandbox 的 agent URI 信息
- 调用 agent 的 getAgentURl,获取 URI 信息
GuestVolumeStats
获取 sandbox 中的指定挂载卷信息
- 校验卷路径是否存在
- 遍历所有容器的所有挂载点,获取挂载源为指定卷目录的 sandbox 内的挂载点
- 调用 agent 的 getGuestVolumeStats,获取卷信息
ResizeGuestVolume
调整 sandbox 中的指定挂载卷大小
- 校验卷路径是否存在
- 遍历所有容器的所有挂载点,获取挂载源为指定卷目录的 sandbox 内的挂载点
- 调用 agent 的 resizeGuestVolume,调整卷大小
GetIPTables
获取 sandbox 的 iptables 信息
- 调用 agent 的 getIPTables,获取 iptables 信息
SetIPTables
设置 sandbox 的 iptables 信息
- 调用 agent 的 setIPTables,设置 iptables 信息
VCContainer
src/runtime/virtcontainers/interfaces.go
virtcontainers 库中用于管理容器的模块。
1 | // Container is composed of a set of containers and a runtime environment. |
工厂函数
- 检验 containerConfig 配置是否合法(containerConfig 取自于 sandboxConfig 中的相关配置)
- 校验 annotation 中 SWAP 资源声明是否合法(io.katacontainers.container.resource.swappiness 必须小于 200),并透传设置(区别于 CPU 和内存等资源,SWAP 无法通过 spec.Containers.Resources 的方式声明,而需要通过 annotation 声明)
- 调用 store 的 FromDisk,获取 sandbox 和容器的状态信息。如果成功获取则表明不是新创建的容器,无需后续动作,仅用于状态更新维护,直接返回容器实例即可
- 处理容器挂载信息,即 container.mounts
除了借助 virtcontainers/kata-agent 的共享目录挂载之外,块设备类型的挂载还可以通过 hypervisor 热添加到 VM。这里仅处理挂载源为块设备类型的挂载信息,常规共享目录挂载仍然由 virtcontainers/kata-agent 处理- 如果未禁用 [hypervisor].disable_block_device_use,则调用 agent 的 capabilities 和 hypervisor 的 Capabilities,根据 agent 是否具备使用块设备能力以及 hypervisor 是否允许块设备热插拔判断是否当前支持块设备
默认场景下,rootfs 由 virtio-fs 传递共享;在未禁用 [hypervisor].disable_block_device_use 时,rootfs 基于块设备创建,并热插到 VM 中使用,以提高性能。例如 devicemapper 场景下 rootfs 必须是基于块设备创建的 - 针对容器中的所有挂载信息
- 如果 mounts.BlockDeviceID 已经存在,则表明已经有一个设备和挂载点相关联,因此不需要创建设备,跳过即可
- 如果挂载类型不是 bind,跳过即可
- 获取 /run/kata-containers/shared/direct-volumes/<base64 mounts.Source>/mountInfo.json 文件,如果文件存在,表明当前挂载设备需要以直通卷的方式处理,即创建 /run/kata-containers/shared/direct-volumes/<base64 mounts.Source>/<sandboxID> 文件,并替换原本 mounts 中 Source、Type、Options、ReadOnly、FSGroup(取自 mountInfo.Metadata[“FSGroup”])、FSGroupChangePolicy(取自 mountInfo.Metadata[“FSGroupChangePolicy”]) 等信息为 mountInfo.json 的对应字段,后续支持直通卷的 CSI 会根据此文件与信息与 Kata 交互
mounts.Source 格式为 /var/lib/kubelet/pods/<podUID>/volumes/kubernetes.io~csi/<pvName>/mount;
mountInfo.json 中 device 字段的格式为 /dev/sda(取决于 host 上的具体设备) - 如果挂载源为块设备类型或 PMEM 设备,则调用 devManager 的 NewDevice,初始化挂载块设备信息,回写 mounts.BlockDeviceID 字段信息
块设备 DeviceInfo 的 HostPath、ContainerPath 等信息均为 mounts 中信息;
PMEM 设备 DeviceInfo 的 HostPath 处理方式比较特殊:如果 DevType 为 c 或者 u,则 backingFile 路径为 /sys/dev/char/<major:minor>/loop/backing_file;如果 DevType 为 b,则 backingFile 路径为 /sys/dev/block/<major:minor>/loop/backing_file。读取 backingFile 文件内容作为 HostPath。此外,判断 HostPath 签名是否合法(当使用 PMEM 设备和 DAX 技术时,需要确保文件或设备路径具有正确的 PFN 签名,以便内核可以正确地管理 PMEM 设备和启用 DAX 技术)以及通过 /proc/mounts 获取 PMEM 的挂载源的文件系统类型 fstype
- 如果未禁用 [hypervisor].disable_block_device_use,则调用 agent 的 capabilities 和 hypervisor 的 Capabilities,根据 agent 是否具备使用块设备能力以及 hypervisor 是否允许块设备热插拔判断是否当前支持块设备
- 准备容器设备信息,即 container.devices
设备均通过 hypervisor 热添加到 VM 中- 针对容器中所有的设备信息,调用 devManager 的 NewDevice,初始化设备信息,并过滤类型为 CDROM 和 floppy 的设备
VCContainer 中声明的 GetAnnotations、GetPid、GetToken、ID、Sandbox 以及 Process 均为参数获取与赋值,无复杂逻辑,不作详述。
start
启动容器
- 校验 sandbox 状态是否为 running
- 校验容器状态是否为 ready 或 stopped
- 调用 agent 的 startContainer,启动容器
- 如果启动失败,则调用 stop,执行回滚操作;否则,设置容器状态为 running,并调用 store 的 ToDisk,保存 sandbox 和容器的状态数据到文件中
stop
关停容器,并清理相关资源
- 如果容器状态已经为 stopped,则不做任何操作
- 校验容器状态是否为 ready、running 或 paused
- 调用 signalProcess,向 sandbox 中的容器进程发送 kill 信号
- 调用 agent 的 waitProcess,确保容器进程已退出
- 调用 agent 的 stopContainer,关停容器
- 针对容器中每一个挂载信息,调用 fsShare 的 UnshareFile,移除 host 侧的 sandbox 共享文件
- 调用 fsShare 的 UnshareRootFilesystem,移除 sandbox 中的容器 rootfs 共享挂载
- 调用 devManager 的 DetachDevice 和 RemoveDevice,detach 并移除 sandbox 中的所有设备(含块设备)
- 如果容器的 rootfs 是块设备,则调用 devManager 的 DetachDevice 和 RemoveDevice,detach 并移除容器的 rootfs 块设备
- 设置容器状态为 stopped,并调用 store 的 ToDisk,保存 sandbox 和容器的状态数据到文件中
delete
删除容器信息
- 校验容器状态是否为 ready 或 stopped
- 删除维护在 sandbox 中的容器信息
- 调用 store 的 ToDisk,保存 sandbox 和容器的状态数据到文件中
signalProcess
向容器进程发送指定信号
- 校验 sandbox 状态是否为 ready 或者 running
- 校验容器状态是否为 ready、running 或者 paused
- 调用 agent 的 signalProcess,向 sandbox 中的指定容器进程发送信号(由于 Containerd 和 CRIO 并不会处理
ESRCH: No such process
错误,因此 Kata runtime 在这里做了特殊操作,针对此报错仅输出 warning 日志,不作返回)
「 Kata Containers 」源码走读 — virtcontainers
http://shenxianghong.github.io/2023/01/30/2023-01-30 Kata Containers 源码走读 - virtcontainers/